package nrpc.client;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

import nrpc.client.reqst.RequestStrategy;
import nrpc.client.reqst.SingleRequestStrategy;
import nrpc.client.serst.ConfiguredServiceStrategy;
import nrpc.client.serst.SubscribeServiceStrategy;
import nrpc.client.skst.SingleSocketStrategy;
import nrpc.client.slst.SingleSelectStrategy;
import nrpc.message.DefaultHandlerMapper;
import nrpc.message.meta.ReturnMessage;
import nrpc.util.AsynMethodTool;

public class ClientStub {

	private RequestStrategy requestStrategy;
	
	public ClientStub(RequestStrategy requestStrategy) {
		this.requestStrategy = requestStrategy;
	}
	
	public ClientStub(String url, InetSocketAddress registerAddress) throws IOException{
		this(new SingleRequestStrategy(
				new SingleSelectStrategy(
						new SubscribeServiceStrategy(registerAddress, url)),
				new SingleSocketStrategy()));
	}
	
	public ClientStub(InetSocketAddress remoteAddress) throws UnknownHostException, IOException {
		this(new SingleRequestStrategy(
				new SingleSelectStrategy(remoteAddress),
				new SingleSocketStrategy()));
	}
	
	public ClientStub(String ip, int port) throws UnknownHostException, IOException {
		this(new InetSocketAddress(ip, port));
	}
	
	public void close() {
		requestStrategy.close();
	}

	@SuppressWarnings("unchecked")
	public <T> T getProxyInstance(Class<T> c) {
		return (T)Proxy.newProxyInstance(c.getClassLoader(), new Class[] {c}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				if(AsynMethodTool.isAsyn(method)) {
					if(AsynMethodTool.isCallback(method)) {
						var ps = method.getParameters();
						Type retType = ((ParameterizedType)ps[ps.length-1].getParameterizedType()).getActualTypeArguments()[0];
						requestStrategy.asynInvoke(method, List.of(args).subList(0, args.length-1), retType, (x)->{
							Consumer<? super Object> consumer = (Consumer<? super Object>)args[args.length-1];
							consumer.accept(((ReturnMessage)x).get());
						});
					}
					else requestStrategy.asynInvoke(method, List.of(args), method.getGenericReturnType(), (x)->{});
					return null;
				}
				else return new Supplier<Object>() {
					Object obj;
					public Object get() {
						List<Object> argList = null;
						if(args == null) argList = new ArrayList<>();
						else argList = List.of(args);
						requestStrategy.invoke(method, argList, method.getGenericReturnType(), (x)->{obj = ((ReturnMessage)x).get();});
						return obj;
					}
				}.get();
			}
		});
	}
	
}
